/* Document.m
 * Purpose:   Initializes, loads, archives and frees a single
 *	document.  The template for the document is located in
 *	Document.nib, which is loaded by this class each time
 *	a new document is created.  The windows are tiled using
 *	the newLocation() function.
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 * Written by: R. Dunbar Poor
 * Created: 28/April/1991
 *
 */
#import "Document.h"
#import "DocController.h"
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/Panel.h>	/* for NX_ALERTxxx */
#import <appkit/SavePanel.h>
#import <appkit/Window.h>	/* for setTitleAsFilename */
#import <objc/hashtable.h>	/* for NXCopyStringBufferFromZone */
#import <sys/param.h>
#import <strings.h>		/* for strncpy() */
#import <stdlib.h>

@interface Document(DocumentPrivate)
- _saveWithNewName:(BOOL)doNewName retainNewName:(BOOL)doRetain;
- _write:(const char *)filename;
- _read:(const char *)filename;
@end

static void newLocation(NXPoint *p)
/*
 * This method computes a new location for each new window created.
 */
{
  static count = 0;
  p->x += (20.0 * count);
  p->y -= (25.0 * count);
  count = (count > 10)? 0 : count+1;
}


@implementation Document

- init
/*
 * The default initialization is simply to open a new (empty) document.
 */
{
  return [self initFromFile:NULL];
}

- initFromFile:(const char *)filename
/*
 * Read a document and initialize it from a file.
 */
{
  [super init];
  [NXApp loadNibSection:"Document.nib" owner:self withNames:NO];
  [self loadFromFile:filename];
  if (!filename) {
    NXRect theFrame;
    /* If its a new document, generate a new position for it */
    [docWindow getFrame:&theFrame];
    newLocation(&theFrame.origin);
    [docWindow moveTo:theFrame.origin.x :theFrame.origin.y];
  }
  [docWindow makeKeyAndOrderFront:self];
  /*
   * I'm not sure why windowDidBecomeMain isn't called as a result of
   * makeKeyAndOrderFront.  At any rate, we want new windows to become
   * the "active" document.
   */
  [self windowDidBecomeMain:self];
  return self;
}

- loadFromFile:(const char *)filename
/*
 * Come here with all the nib objects instantiated.  We initialize any
 * application-specific state from the contents from the given filename,
 * and finish up any initialization.
 */
{
  if (filename) {
    if (![self _read:filename]) {
      [self free];	/* couldn't load document file */
      return nil;
    }
  }

  /*
   * do some common setup.
   */
  [self setDocumentName:filename];
  [self setDocEdited:NO];

  return self;
}

- free
{
  /* tell the controller that we can no longer be the active doucment */
  [[NXApp delegate] unsetActiveDocument:self];
  if (name) free(name);
  [docWindow free];
  return [super free];
}

- (const char *)message
{
  return [docContents stringValue];
}

- activateDocument:sender
{
  [statusField setStringValue:"Active..."];
  return self;
}

- deactivateDocument:sender
{
  [statusField setStringValue:"Inactive..."];
  return self;
}

- hideDocument:sender
{
  [docWindow orderOut:sender];
  return self;
}

- setDocEdited:(BOOL)edited
{
  [docWindow setDocEdited:edited];
  /*
   * The following is a hack to tell the controller to update the
   * Revert menu cell.  Unfortunately, it does lots of work besides
   * that...
   */
  if ([[NXApp delegate] activeDocument] == self) {
    [[NXApp delegate] setActiveDocument:self];
  }
  return self;
}

- (BOOL)isDocEdited
{
  return [docWindow isDocEdited];
}

- setDocumentName:(const char *)newName
{
  /*
   * If we are passing 'name' itself as  the newName argument, as will happen
   * in the revert: method, we don't want to modify it.  In particular, we
   * better not call free() on it and then try to copy it back to itself (which
   * was a bug I had for a while.)
   */
  if (newName != name) {
    /* name isn't the same as newName, so it is safe to free name now */
    if (name) free(name);
    if (newName) {
      name = NXCopyStringBuffer(newName);
    } else {
      name = NULL;
    }
  }

  if (name) {
    [docWindow setTitleAsFilename:name];
  } else {
    [docWindow setTitleAsFilename:"Untitled Document"];
  }

  return self;
}

- save:sender
{
  if ([self isDocEdited] || !name) {
    [self _saveWithNewName:NO retainNewName:YES];
  }
  return self;
}
- saveAs:sender {  return [self _saveWithNewName:YES retainNewName:YES]; }
- saveTo:sender {  return [self _saveWithNewName:YES retainNewName:NO];  }

- revert:sender
{
  int choice;

  if ([self isDocEdited]) {
    choice = NXRunAlertPanel(
		"Revert",
		"Discard changes to the document?",
		"Revert",
		"Cancel",
		NULL);
    switch (choice) {
      case NX_ALERTDEFAULT:
	[self loadFromFile:name];
	break;
      case NX_ALERTOTHER:
	return nil;
    }
  }
  return self;
}

- close:sender
{
  [docWindow performClose:self];
  return sender;
}

- dirty:sender
{
  [self setDocEdited:YES];
  return [self windowDidBecomeMain:self];
}

- checkForEdited:sender
/*
 * If the document is edited, give the user a chance to save the
 * document.  Returns nil if they want to cancel.
 */
{
  int choice;

  if ([self isDocEdited]) {
    [docWindow makeKeyAndOrderFront:self];
    choice = NXRunAlertPanel(
		"Close",
		"Save changes to %s?",
		"Save",		/* NX_ALERTDEFAULT */
		"Don't Save",	/* NX_ALERTALTERNATE */
		"Cancel",	/* NX_ALERTOTHER */
		(name)?name:"Untitled Document");
    switch (choice) {
      case NX_ALERTALTERNATE:
	break;
      case NX_ALERTDEFAULT:
	[self save:nil];
	break;
      case NX_ALERTOTHER:
	return nil;
    }
  }
  return self;
}

- windowDidBecomeMain:sender
{
  [[NXApp delegate] setActiveDocument:self];
  return [self activateDocument:sender];
}

- windowDidResignMain:sender
{
  return [self deactivateDocument:sender];
}

- windowWillClose:sender
{
  if (![self checkForEdited:sender]) return nil;

  /* make the document disavow any knowledge of the window */
  [docWindow setDelegate:nil];
  docWindow = nil;

  /* The document can't live now that its window is gone... */
  [self free];
  return sender;
}

/*
 * All varieties of save go through this routine.  It covers all the cases
 * of running the Save Panel and retaining the name chosen.
 */
- _saveWithNewName:(BOOL)doNewName retainNewName:(BOOL)doRetain
{
  id savePanel;
  const char *saveName;		/* filename to save into */

  if (!name || doNewName) {
    /* saveAs or saveTo */
    savePanel = [SavePanel new];
    [savePanel setRequiredFileType:DOCUMENT_TYPE];
    if ([savePanel runModalForDirectory:NULL file:name]) {
      saveName = [savePanel filename];
    } else {
      /* aborted out? */
      return self;
    }
  } else {
    /* ordinary Save */
    saveName = name;
  }
  [self _write:saveName];

  /* update the document name if requested */
  if (doRetain) {
    [self setDocumentName:saveName];
    [docWindow setDocEdited:NO];
  }
  return self;
}

- _write:(const char *)filename
{
  NXTypedStream *ts;
  NXRect theFrame;
  const char *contents;

  ts = NXOpenTypedStreamForFile(filename, NX_WRITEONLY);
  [docWindow getFrame:&theFrame];
  NXWriteRect(ts, &theFrame);
  contents = [docContents stringValue];
  NXWriteType(ts, "*", &contents);
  NXCloseTypedStream(ts);

  return self;
}

- _read:(const char *)filename
{
  NXTypedStream *ts;
  NXRect theFrame;
  char *contents;

  ts = NXOpenTypedStreamForFile(filename, NX_READONLY);
  NXReadRect(ts, &theFrame);
  [docWindow placeWindowAndDisplay:&theFrame];
  NXReadType(ts, "*", &contents);
  [docContents setStringValue:contents];
  NXCloseTypedStream(ts);

  return self;
}

@end
